<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<title>双向数据绑定实验</title>
		<link rel="stylesheet" type="text/css" href="css/vui.css"/>
		
	</head>
	<body>
		<header class="header">
			<!-- <a class="vui-back"><svg width="10" height="16" viewBox="0,0,10,16" fill="transparent"><path d="M10 0 L0 8 L10 16" stroke="currentColor"></path></svg></a> -->
			<a class="title">MVVM</a>
			<a class="vui-menu">菜单</a>
		</header>
		<div class="container">
			<div id="wrap">
				<input type="text" v-model="name" />
				<h1 v-html="name"></h1>
				<p v-class="color">{{name}}</p>
				<button type="button" v-on:click="sayHi">change model</button>
			</div>
		</div>
		
		<script type="text/javascript">
			
			window.onload = function() {
				
				function MVVM(options) {
					this.opts = options;
					this.data = this.opts.data,
						me = this;
					Object.keys(this.data).forEach(function(k) {
						me._proxy(k);
					})
					new Observer(this.data);
					this.compile = new Compile(this.opts.el || document.body, this);
				}
				MVVM.prototype = {
					_proxy(k) {
						var me = this;
						Object.defineProperty(me, k, {
							configurable: false,
							enumerable: true,
							get: function proxyGetter() {
								return me.data[k];
							},
							set: function proxySetter(newVal) {
								me.data[k] = newVal;
							}
						})
					}
				}
				/**
				 * @des 数据劫持监测
				 */
				function Observer(data) {
					this.data = data;
					this.walk();
				}
				Observer.prototype = {
					walk() {
						var me = this;
						Object.keys(this.data).forEach(function(k) {
							me.defineActive(me.data, k, me.data[k]);
						});
					},
					defineActive(d, k, v) {
						var dep = new Dep();
						Object.defineProperty(d, k, {
							configurable: false,
							enumerable: true,
							get: function() {
								Dep.target && dep.depend();
								return v;
							},
							set: function(newVal) {
								console.log('值发生了变化。。。', v, '---', newVal);
								if(newVal === v) return;
								v = newVal;
								dep.notify();
							}
						})
					}
				}
				/**
				 * @des 订阅器
				 */
				var uid = 0;
				function Dep() {
					this.id = uid++;
					this.subs = [];
				}
				Dep.prototype = {
					addSub(sub) {
						this.subs.push(sub);
					},
					depend() {
						Dep.target.addDep(this);
					},
					notify() {
						console.log(this.subs)
						this.subs.forEach(function(sub) {
							sub.update();
						})
					}
				}
				/**
				 * @des watcher
				 */
				function Watcher(vm, exp, cb) {
					this.vm = vm;
					this.exp = exp;
					this.cb = cb;
					this.depIds = {};
					
					this.value = this.get();
				}
				Watcher.prototype = {
					update() {
						var newVal = this.get(),
							oldVal = this.value;
						if(newVal !== oldVal) {
							this.value = newVal;
						}
						this.cb.call(this.vm, newVal, oldVal);
					},
					addDep(dep) {
						if(!(this.depIds.hasOwnProperty(dep.id))) {
							dep.addSub(this);
							this.depIds[dep.id] = dep;
						}
					},
					get() {
						Dep.target = this;
						var value = this.vm[this.exp];
						Dep.target = null;
						return value;
					}
				}
				/**
				 * @des compile
				 */
				function Compile(el, vm) {
					this.vm = vm;
					this.el = this.isElementNode(el) ? el : document.querySelector(el);
					if(this.el) {
						this.$fragment = this.nodeToFragment(this.el);
						this.compile(this.$fragment);
						this.el.appendChild(this.$fragment);
					}
				}
				Compile.prototype = {
					nodeToFragment(node) {
						var fragment = document.createDocumentFragment(),
							child;
						while(child = node.firstChild) {
							fragment.appendChild(child);
						}
						return fragment;
					},
					compile(node) {
						var children = node.childNodes,
							reg = /\{\{(.*)\}\}/,
							me = this;
						[].slice.call(children).forEach(function(node) {
							var text = node.textContent;
							if(me.isElementNode(node)) {
								me.compileElement(node);
							}else if(me.isTextNode(node) && reg.test(text)) {
								me.compileText(node, RegExp.$1);
							}
							if(node.childNodes && node.childNodes.length) {
								me.compile(node);
							}
						})
					},
					compileElement(node) {
						var nodeAttr = node.attributes,
							me = this;
						[].slice.call(nodeAttr).forEach(function(attr) {
							var attrName = attr.name;
							if(me.isDirective(attrName)) {
								var dir = attrName.substring(2),
									exp = attr.value;
								if(me.isEventDirective(dir)) {
									directiveUtil.eventHandler(me.vm, node, exp, dir);
								}else {
									directiveUtil[dir] && directiveUtil[dir](me.vm, node, exp);
								}
							}
						})
					},
					compileText(node, exp) {
						directiveUtil.text(this.vm, node, exp);
					},
					isElementNode(node) {
						return node.nodeType === 1;
					},
					isTextNode(node) {
						return node.nodeType === 3;
					},
					isDirective(attr) {
						return attr.indexOf('v-') === 0;
					},
					isEventDirective(dir) {
						return dir.indexOf('on') === 0;
					}
				}
				/**
				 * @des 指令集
				 */
				var directiveUtil = {
					text(vm, node, exp) {
						this.bind(vm, node, exp, 'text');
					},
					model(vm, node, exp) {
						this.bind(vm, node, exp, 'model');
						var me = this;
						node.addEventListener('input', function(e) {
							var newVal = e.target.value;
							console.log(newVal);
							me.setVMVal(vm, exp, newVal);
							
						}, false);
					},
					html(vm, node, exp) {
						
					},
					class(vm, node, exp) {
						
					},
					eventHandler(vm, node, exp, dir) {
						var eventType = dir.split(':')[1],
							fn = vm.opts.methods && vm.opts.methods[exp];
						if(fn && eventType) {
							console.log(node);
							node.addEventListener(eventType, fn.bind(vm), false);
						}
					},
					bind(vm, node, exp, dir) {
						var updateFn = updater[dir + 'Update'];
						updateFn && updateFn(node, this.getVMVal(vm, exp));
						new Watcher(vm, exp, function(newVal, oldVal) {
							updateFn && updateFn(node, newVal, oldVal);
						});
					},
					getVMVal(vm, exp) {
						var val = vm;
						exp = exp.split('.');
						exp.forEach(function(k) {
							val = val[k];
						})
						return val;
					},
					setVMVal(vm, exp, newVal) {
						var val = vm;
						exp = exp.split('.');
						exp.forEach(function(k, i) {
							if(i < (exp.length-1)) {
								val = val[k];
							}else {
								val[k] = newVal;
							}
						})
					}
				};
				/**
				 * @des 更新集
				 */
				var updater = {
					textUpdate(node, value) {
						node.textContent = typeof value === 'undefined' ? '' : value;
					},
					modelUpdate(node, value) {
						node.value = typeof value === 'undefined' ? '' : value;
					}
				};
				
				
				new MVVM({
					el: '#wrap',
					data: {
						name: 'vikeel',
						obj: {
							next: 'cleaner',
						},
					},
					methods: {
						sayHi() {
							this.name = 'qing';
						}
					}
				});
				
			}
			
		</script>
	</body>
</html>
